Skip to content

perf(build): parallelize fragmented backing reads#2872

Open
ValentaTomas wants to merge 6 commits into
mainfrom
perf/parallel-fragmented-build-read
Open

perf(build): parallelize fragmented backing reads#2872
ValentaTomas wants to merge 6 commits into
mainfrom
perf/parallel-fragmented-build-read

Conversation

@ValentaTomas
Copy link
Copy Markdown
Member

@ValentaTomas ValentaTomas commented May 30, 2026

Parallelize fragmented build reads when a request spans multiple backing mappings, instead of reading them serially. Gated by max-parallel-build-read-segments (default 1 = serial). Applies to both memfile and rootfs via build.File.ReadAt, and to multi-mapping Slice (which falls back through ReadAt).

Serial and parallel paths share one mapping planner and segment reader; cache eviction or a peer transition re-resolves and retries.

@cla-bot cla-bot Bot added the cla-signed label May 30, 2026
@cursor
Copy link
Copy Markdown

cursor Bot commented May 30, 2026

PR Summary

Medium Risk
Touches core snapshot/build read path and concurrent writes into the same buffer; misconfiguration or races could corrupt reads, though default serial mode limits exposure.

Overview
Fragmented build reads can run backing segment I/O in parallel when max-parallel-build-read-segments is above 1; default 1 keeps today’s serial behavior. ReadAt now plans mapped segments (including zero-filled holes), then executes them with an optional errgroup limit, and still retries the whole read on cache eviction or header peer transition.

Reviewed by Cursor Bugbot for commit a607c1a. Bugbot is set up for automated code reviews on this repo. Configure here.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: Parallel path skips planned reads
    • Modified planRead to return accumulated segments with io.EOF when progress has been made, readAtParallel to execute segments even with EOF, and ReadAt to properly propagate partial read counts.

Create PR

Or push these changes by commenting:

@cursor push edbb24779f
Preview (edbb24779f)
diff --git a/packages/orchestrator/pkg/sandbox/build/build.go b/packages/orchestrator/pkg/sandbox/build/build.go
--- a/packages/orchestrator/pkg/sandbox/build/build.go
+++ b/packages/orchestrator/pkg/sandbox/build/build.go
@@ -64,6 +64,8 @@
 	if maxParallel > 1 && len(p) > 0 {
 		if err := b.readAtParallel(ctx, p, off, maxParallel); err == nil {
 			return len(p), nil
+		} else if err == io.EOF {
+			return len(p), io.EOF
 		} else if shouldRetrySerial(err) {
 			if retry, swapErr := b.retryOnTransition(ctx, err); !retry && swapErr != nil {
 				return 0, swapErr
@@ -198,10 +200,13 @@
 }
 
 func (b *File) readAtParallel(ctx context.Context, p []byte, off int64, maxParallel int) error {
-	segments, err := b.planRead(ctx, p, off)
-	if err != nil {
-		return err
+	segments, planErr := b.planRead(ctx, p, off)
+	if planErr != nil && planErr != io.EOF {
+		return planErr
 	}
+	if len(segments) == 0 {
+		return planErr
+	}
 	if len(segments) <= 1 {
 		for _, s := range segments {
 			n, err := s.diff.ReadAt(ctx, p[s.dstOff:s.dstOff+int(s.length)], s.srcOff, s.ft)
@@ -213,7 +218,7 @@
 			}
 		}
 
-		return nil
+		return planErr
 	}
 
 	g, gctx := errgroup.WithContext(ctx)
@@ -233,7 +238,11 @@
 		})
 	}
 
-	return g.Wait()
+	if err := g.Wait(); err != nil {
+		return err
+	}
+
+	return planErr
 }
 
 func (b *File) planRead(ctx context.Context, p []byte, off int64) ([]readSegment, error) {
@@ -255,6 +264,9 @@
 		}
 		readLength := min(int64(mappedToBuild.Length), int64(len(p)-n))
 		if readLength <= 0 {
+			if n > 0 {
+				return segments, io.EOF
+			}
 			return nil, io.EOF
 		}
 		if mappedToBuild.BuildId == uuid.Nil {

You can send follow-ups to the cloud agent here.

Reviewed by Cursor Bugbot for commit dbbe148. Configure here.

Comment thread packages/orchestrator/pkg/sandbox/build/build.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented May 30, 2026

❌ 4 Tests Failed:

Tests completed Failed Passed Skipped
2705 4 2701 5
View the full list of 4 ❄️ flaky test(s)
github.com/e2b-dev/infra/tests/integration/internal/tests/api/sandboxes::TestSandboxListPaginationRunningLargerLimit

Flake rate in main: 42.80% (Passed 747 times, Failed 559 times)

Stack Traces | 92.8s run time
=== RUN   TestSandboxListPaginationRunningLargerLimit
    sandbox_list_test.go:327: Created sandbox 1/12: iacy98b41fcmwj2v5pbbw
    sandbox_list_test.go:327: Created sandbox 2/12: i5qt5ka8ydyvtse6z9bdn
    sandbox_list_test.go:327: Created sandbox 3/12: iqj8ri1cheka5nmq8gns3
    sandbox_list_test.go:327: Created sandbox 4/12: imwmdk3am5wmotr51lz0s
    sandbox_list_test.go:327: Created sandbox 5/12: i9p56qxqpy9hc62pul3w3
    sandbox_list_test.go:327: Created sandbox 6/12: iz6hsujubkxpz4hsq9lyj
    sandbox_list_test.go:327: Created sandbox 7/12: id6p28ag2k8z52y0kzaqr
    sandbox_list_test.go:327: Created sandbox 8/12: i9q6oo6xc0636p7v9m5p7
    sandbox_list_test.go:327: Created sandbox 9/12: ii6zdfz1e0c9985n63nnz
    sandbox_list_test.go:327: Created sandbox 10/12: inj6yukv82hn3yvp7843x
    sandbox_list_test.go:327: Created sandbox 11/12: i9rowdd0ri3z1g6xzjcng
    sandbox_list_test.go:327: Created sandbox 12/12: ip4kmzl0lt27x1zyk1b47
    sandbox_list_test.go:330: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:340
        	            				.../hostedtoolcache/go/1.26.3.../src/runtime/asm_amd64.s:1771
        	Error:      	"[]" should have 12 item(s), but has 0
    sandbox_list_test.go:330: 
        	Error Trace:	.../api/sandboxes/sandbox_list_test.go:330
        	Error:      	Condition never satisfied
        	Test:       	TestSandboxListPaginationRunningLargerLimit
--- FAIL: TestSandboxListPaginationRunningLargerLimit (92.84s)
github.com/e2b-dev/infra/tests/integration/internal/tests/envd::TestCommandKillNextApp

Flake rate in main: 42.86% (Passed 736 times, Failed 552 times)

Stack Traces | 302s run time
=== RUN   TestCommandKillNextApp
=== PAUSE TestCommandKillNextApp
=== CONT  TestCommandKillNextApp
Executing command /bin/bash in sandbox ii4w6lm4jpl454w9qfl9n
    process_test.go:28: Command [npx] output: event:{start:{pid:1256}}
    process_test.go:28: Command [npx] output: event:{data:{stderr:"npm WARN exec The following package was not found and will be installed: create-next-app@16.2.6\n"}}
Executing command /bin/bash in sandbox iycub94c3kicpdpziqytm
    process_test.go:28: Command [npx] output: event:{data:{stdout:"Creating a new Next.js app in .../home/user/nextapp.\n\nUsing npm.\n\nInitializing project with template: app-tw \n\n"}}
    process_test.go:28: Command [npx] output: event:{data:{stdout:"\nInstalling dependencies:\n"}}
    process_test.go:28: Command [npx] output: event:{data:{stdout:"- next\n"}}
    process_test.go:28: Command [npx] output: event:{data:{stdout:"- react\n"}}
    process_test.go:28: Command [npx] output: event:{data:{stdout:"- react-dom\n\nInstalling devDependencies:\n- @tailwindcss/postcss\n- @types/node\n- @types/react\n- @types/react-dom\n- eslint\n- eslint-config-next\n- tailwindcss\n- typescript\n\n"}}
    process_test.go:29: 
        	Error Trace:	.../tests/envd/process_test.go:29
        	Error:      	Received unexpected error:
        	            	failed to execute command npx in sandbox iv6kjqd109tu7yc6k4xkr: invalid_argument: protocol error: incomplete envelope: unexpected EOF
        	Test:       	TestCommandKillNextApp
--- FAIL: TestCommandKillNextApp (301.86s)
github.com/e2b-dev/infra/tests/integration/internal/tests/orchestrator::TestSandboxMemoryIntegrity

Flake rate in main: 57.71% (Passed 740 times, Failed 1010 times)

Stack Traces | 64.5s run time
=== RUN   TestSandboxMemoryIntegrity
=== PAUSE TestSandboxMemoryIntegrity
=== CONT  TestSandboxMemoryIntegrity
    sandbox_memory_integrity_test.go:27: Build completed successfully
--- FAIL: TestSandboxMemoryIntegrity (64.54s)
github.com/e2b-dev/infra/tests/integration/internal/tests/orchestrator::TestSandboxMemoryIntegrity/tmpfs_hash

Flake rate in main: 57.83% (Passed 730 times, Failed 1001 times)

Stack Traces | 203s run time
=== RUN   TestSandboxMemoryIntegrity/tmpfs_hash
=== PAUSE TestSandboxMemoryIntegrity/tmpfs_hash
=== CONT  TestSandboxMemoryIntegrity/tmpfs_hash
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{start:{pid:1250}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stdout:"Total memory: 985 MB\nUsed memory before tmpfs mount: 193 MB\nFree memory before tmpfs mount: 791 MB\nMemory to use in integrity test (60% of free, min 64MB): 474 MB\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"474+0 records in\n474+0 records out\n497025024 bytes (497 MB, 474 MiB) copied, 2.17024 s, 229 MB/s\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"\tCommand being timed: \"dd if=/dev/urandom of=/mnt/testfile bs=1M count=474\"\n\tUser time (seconds): 0.00\n\tSystem time (seconds): 2.16\n\tPercent of CPU this job got: 99%\n\tElapsed (wall clock) time (h:mm:ss or m:ss): 0:02.17\n\tAverage shared text size (kbytes): 0\n\tAverage unshared data size (kbytes): 0\n\tAverage stack size (kbytes): 0\n\tAverage total size (kbytes): 0\n\tMaximum resident set size (kbytes): 2684\n\tAverage resident set size (kbytes): 0\n\tMajor (requiring I/O) page faults: 3\n\tMinor (reclaiming a frame) page faults: 344\n\tVoluntary context switches: 4\n\tInvoluntary context switches: 13\n\tSwaps: 0\n\tFil"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"e system inputs:"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:" 176\n\tFil"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"e syst"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"em out"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"puts:"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:" 0\n\tS"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"ocke"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"t mes"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"sages"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:" sent"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:": 0\n\t"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"Socke"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"t mes"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"sages"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:" rece"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"ived:"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:" 0\n\tS"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"ignal"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"s del"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"iver"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"ed: 0"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"\n\tPag"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"e siz"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"e (by"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:"tes):"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stdout:"Used memory after tmpfs mount and file fill: 671 MB\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{data:{stderr:" 4096\n\tExit status: 0\n"}}
    sandbox_memory_integrity_test.go:70: Command [bash] output: event:{end:{exited:true  status:"exit status 0"}}
    sandbox_memory_integrity_test.go:70: Command [bash] completed successfully in sandbox ir7rt5zsvuqgwd833dof7
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{start:{pid:1267}}
Executing command bash in sandbox ij73r1jhf9k0qf6ysi3g7 (user: root)
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{data:{stdout:"2c9647470a1e2ed592c3b13eab532cf9f3af29560e653edcfbbc60b295f6ad1a\n"}}
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{end:{exited:true  status:"exit status 0"}}
    sandbox_memory_integrity_test.go:80: Command [bash] completed successfully in sandbox ir7rt5zsvuqgwd833dof7
    sandbox_memory_integrity_test.go:80: Command [bash] output: event:{start:{pid:1270}}
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
Executing command bash in sandbox ir7rt5zsvuqgwd833dof7 (user: root)
    sandbox_memory_integrity_test.go:110: 
        	Error Trace:	.../tests/orchestrator/sandbox_memory_integrity_test.go:81
        	            				.../hostedtoolcache/go/1.26.3.../src/runtime/asm_amd64.s:1771
        	Error:      	Received unexpected error:
        	            	failed to execute command bash in sandbox ir7rt5zsvuqgwd833dof7: unavailable: HTTP status 502 Bad Gateway
    sandbox_memory_integrity_test.go:110: 
        	Error Trace:	.../tests/orchestrator/sandbox_memory_integrity_test.go:78
        	            				.../tests/orchestrator/sandbox_memory_integrity_test.go:110
        	Error:      	Condition never satisfied
        	Test:       	TestSandboxMemoryIntegrity/tmpfs_hash
--- FAIL: TestSandboxMemoryIntegrity/tmpfs_hash (203.02s)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Calling CompressionType() directly on the result of h.GetBuildFrameData(buildID) will cause a nil pointer dereference panic if the build is uncompressed, as GetBuildFrameData returns nil in that case. To prevent this, check if the returned frame table is non-nil before retrieving its compression type.

Comment thread packages/orchestrator/pkg/sandbox/build/build.go Outdated
@ValentaTomas ValentaTomas marked this pull request as ready for review May 30, 2026 21:26
Copy link
Copy Markdown
Contributor

@dobrac dobrac left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation complexity is quite high - not sure if needs to be.

One nit otherwise with nil handling

Comment thread packages/orchestrator/pkg/sandbox/build/build.go Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants